<?php
/**
 * CONTENIDO extension pack (mpContenidoX), consists of multiple classes to
 * provide more features for Plugins/Modules.
 *
 * @package     mpContenidoX
 * @subpackage  Model
 * @author      Murat Purc <murat@purc.de>
 * @copyright   Copyright (c) 2008-2012 Murat Purc (http://www.purc.de)
 * @license     http://www.gnu.org/licenses/gpl-2.0.html - GNU General Public License, version 2
 * @version     $Id: Abstract.class.php 21 2012-07-16 21:33:14Z purcaholic $
 */


defined('CON_FRAMEWORK') or die('Illegal call');


/**
 * Abstract table class.
 *
 * Note: This implementation is neither active record nor orm pattern, it's just
 * a simple way to access to tables using a wrapper class.
 *
 * Usage:
 * <code>
 * class SomeTable extends mpTable_Abstract
 * {
 *     public function __construct()
 *     {
 *         parent::__construct('table_name', 'primary_key_name');
 *     }
 *
 *     public function insert(array $aData)
 *     {
 *         // insert code
 *     }
 *
 *     public function update(array $aData)
 *     {
 *         // insert code
 *     }
 * }
 * </code>
 *
 * @package     mpContenidoX
 * @subpackage  Model
 * @author      Murat Purc <murat@purc.de>
 */
abstract class mpTable_Abstract
{

    /**
     * Database class instance
     * @var  DB_Contenido
     */
    protected $_db;

    /**
     * Table name
     * @var  string
     */
    protected $_table = '';

    /**
     * Primary key
     * @var  string
     */
    protected $_pk = '';

    /**
     * Flag to use sequence table for getting next id
     * @var  bool
     */
    protected $_useSequence = false;

    /**
     * Configuration array
     * @var  array
     */
    protected $_cfg;

    /**
     * Constuctor
     *
     * @param  string  $table  Table name
     * @param  string  $pk     Name of field which is the primary key
     */
    protected function __construct($table, $pk)
    {
        $this->_db = new DB_Contenido();
        $this->_db->connect();
        $this->_table = (string) $table;
        $this->_pk    = (string) $pk;
        $this->_cfg   = & $GLOBALS['cfg'];
    }


    /**
     * Recordset existance check by primary key
     *
     * @param   mixed  $id  Primary key
     * @return  bool  True if recordset exists, otherwhise false
     */
    public function exists($id)
    {
        $sql = "SELECT `" . $this->_pk . "` FROM `" . $this->_table . "` WHERE `" . $this->_pk . "`='" . $id . "';";
        if (!$this->_db->query($sql)) {
            return false;
        }
        return ($this->_db->next_record()) ? true : false;
    }


    /**
     * Counts recordsets
     *
     * @return  int  Number of recordsets
     */
    public function countEntries()
    {
        $sql = "SELECT COUNT(`" . $this->_pk . "`) AS `num` FROM `" . $this->_table . "`;";
        if (!$this->_db->query($sql)) {
            return 0;
        }
        return ($this->_db->next_record()) ? (int) $this->_db->f('num') : 0;
    }


    /**
     * Returns recordset by primary key
     *
     * @param   mixed  $id  Primary key
     * @return  mixed  Assoziative array or null on failure
     */
    public function get($id)
    {
        $sql = "SELECT * FROM `" . $this->_table . "` WHERE `" . $this->_pk . "`='" . $id . "';";
        return $this->_queryAndGetFirst($sql);
    }


    /**
     * Returns random recordset
     *
     * @return  mixed  Assoziative array or null on failure
     */
    public function getRandom()
    {
        $sql = "SELECT * FROM `" . $this->_table . "` ORDER BY RAND() LIMIT 1;";
        return $this->_queryAndGetFirst($sql);
    }


    /**
     * Returns number of entry pages.
     *
     * @param   int     $numPerPage  Number of entries per page (default value is 10)
     * @param   string  $orderBy     The fieldname to order (default order field is primary key)
     * @param   string  $orderDir    Order direction (ASC ot DESC, default is ASC)
     * @return  int     The number of pages
     */
    public function getEntryPages($numPerPage=null)
    {
        if (!$entries = $this->countEntries()) {
            return 0;
        }

        if ((int) $numPerPage <= 0) {
            $numPerPage = 10;
        }

        return ceil($entries / $numPerPage);
    }


    /**
     * Returns a range of entries, usable for a pager.
     *
     * @param   int     $page        The page number to get (e. g. 1 for first page, 2 for the second, etc...)
     * @param   int     $numPerPage  Number of entrie per page (default value is 10)
     * @param   string  $orderBy     The fieldname to order (default order field is primary key)
     * @param   string  $orderDir    Order direction (ASC ot DESC, default is ASC)
     * @return  array|null   The entries array or null
     */
    public function getEntryPage($page, $numPerPage=null, $orderBy=null, $orderDir=null)
    {
        $page = ((int) $page) - 1;
        if ($page < 0) {
            return null;
        }

        if ((int) $numPerPage <= 0) {
            $numPerPage = 10;
        }

        $startPos = ($page * $numPerPage);

        if ($orderBy == null) {
            $orderBy = $this->_pk;
        }
        $orderDir = ($orderDir == 'DESC') ? 'DESC' : 'ASC';

        $sql = "SELECT * FROM `" . $this->_table . "` ORDER BY $orderBy $orderDir LIMIT $startPos, $numPerPage;";
        return $this->_queryAndGet($sql);
    }


    /**
     * Returns recordset (first occurance) by passed fieldname and it's value (foo=bar)
     *
     * NOTE: Passed value should escaped before!
     *
     * @param   string  $field  Field name
     * @param   mixed   $value  The value
     * @return  mixed  Assoziative array or null on failure
     */
    public function getByField($field, $value)
    {
        $value = $this->_prepareData($value);
        $sql = "SELECT * FROM `" . $this->_table . "` WHERE `" . $field . "`='" . $value . "' LIMIT 1;";
        return $this->_queryAndGetFirst($sql);
    }


    /**
     * Returns all found recordsets by passed fieldname and it's value (foo=bar)
     *
     * NOTE: Passed value should escaped before!
     *
     * @param   string  $field  Field name
     * @param   mixed   $value  The value
     * @return  mixed  Indexed array containing assoziative array or null on failure
     */
    public function getAllByField($field, $value)
    {
        $value = $this->_prepareData($value);
        $sql = "SELECT * FROM `" . $this->_table . "` WHERE `" . $field . "`='" . $value . "';";
        return $this->_queryAndGet($sql);
    }


    /**
     * Updates a record
     *
     * @param   array  $data  Assoziative recordset array
     * @param   mixed  $id    Primary key
     * @return  bool
     */
    protected function update(array $data, $id)
    {
        $statement = '';
        foreach ($data as $f => $v) {
            $statement .= "`" . $f . "`='" . $this->_prepareData($v) . "', ";
        }
        $statement = substr($statement, 0, -2);

        $sql = "UPDATE `" . $this->_table . "` SET $statement WHERE `" . $this->_pk . "`='" . $this->_prepareData($id) . "';";
        $this->_db->query($sql);
        return ($this->_db->affected_rows());
    }


    /**
     * Inserts a record
     *
     * @param   array  $data  Assoziative recordset array
     * @return  int|null  Id of recordset
     */
    protected function insert(array $data)
    {
        $fields = '';
        $values = '';

        if ($this->_useSequence && !isset($data[$this->_pk])) {
            $data[$this->_pk] = $this->_db->nextid($this->_table);
        }

        foreach ($data as $f => $v) {
            $fields .= '`' . $f . '`, ';
            $values .= "'" . $this->_prepareData($v) . "', ";
        }

        $fields = substr($fields, 0, -2);
        $values = substr($values, 0, -2);

        $sql = "INSERT INTO `" . $this->_table . "` (" . $fields . ") VALUES (" . $values . ");";
        $this->_db->query($sql);
        if ($this->_db->affected_rows()) {
            return $data[$this->_pk];
        } else {
            return null;
        }
    }


    /**
     * Deletes a recordset by passed primary key
     *
     * @param  mixed  $id  Primary key of record set to dele
     */
    public function delete($id)
    {
        $id = $this->_prepareData($id);
        $sql = "DELETE FROM `" . $this->_table . "` WHERE `" . $this->_pk . "`='" . $id . "';";
        $this->_db->query($sql);
        return ($this->_db->affected_rows());
    }


    /**
     * Deletes all recordsets matching passed fieldname and it's value (foo=bar).
     *
     * @param   string  $field  Field name
     * @param   mixed   $value  The value
     */
    public function deleteByField($field, $value)
    {
        $value = $this->_prepareData($value);
        $sql = "DELETE FROM `" . $this->_table . "` WHERE `" . $field . "`='" . $value . "';";
        $this->_db->query($sql);
        return ($this->_db->affected_rows());
    }


    /**
     * Returns current record, if available
     *
     */
    public function getRecord($resultType=MYSQL_ASSOC)
    {
        if (!$this->_db->Record) {
            return null;
        }
        $rs = $this->_db->Record;
        foreach ($rs as $k => $v) {
            if ($resultType == MYSQL_ASSOC && is_numeric($k)) {
                unset($rs[$k]);
            } elseif ($resultType == MYSQL_NUM && !is_numeric($k)) {
                unset($rs[$k]);
            }
        }
        return $rs;
    }


     /**
     * Prepares passed data for usage in a sql statement by escaping string values.
     *
     * @param   string|array  $data  Either the data as string or a array strings
     * @return  string|array
     */
    protected function _prepareData($data)
    {
//        return $data;
        if (is_array($data)) {
            foreach ($data as $key => $value) {
                if (is_string($value)) {
                    $data[$key] = $this->_escape($value);
                }
            }
        } else if (is_string($data)) {
            $data = $this->_escape($data);
        }
        return $data;
    }


    /**
     * Escapes some character for use in SQL statement.
     *
     * @param   string  $string
     * @return  string
     */
    protected function _escape($string)
    {
        return $this->_db->escape($string);

        // NOTE: alternative to mysql_real_escape_string
        // mysql_real_escape_string doesn't work correct sometimes due to unknows reasons???
        return strtr($string, array(
          "\x00" => '\x00',
          "\n"   => '\n',
          "\r"   => '\r',
          '\\'   => '\\\\',
          "'"    => "\'",
          '"'    => '\"',
          "\x1a" => '\x1a'
        ));
    }


    /**
     * Performs the query and returns the first result as a assoziative array
     *
     * @param   string  $sql
     * @return  array|null
     */
    protected function _queryAndGetFirst($sql)
    {
        if (!$this->_db->query($sql)) {
            return null;
        }
        return ($this->_db->next_record()) ? $this->getRecord() : null;
    }


    /**
     * Performs the query and returns the result as a indexed array containig a
     * assoziative array
     *
     * @param   string  $sql
     * @return  array|null
     */
    protected function _queryAndGet($sql)
    {
        if (!$this->_db->query($sql)) {
            return null;
        }
        $rs = array();
        while ($this->_db->next_record()) {
            $rs[] = $this->getRecord();
        }
        return $rs;
    }

}

